FFI
FFI
(Foreign Function Interface),顾名思义,是Rust中用来调用其他语言的机制。值得注意的是,FFI
在C和Rust之间实现了零抽象开销,也就是说在Rust调用C和C调用Rust时并没有运行时的花销。
一个例子
比方说我们定义了一个函数:
1 | int double_input(int input) { |
如果我们需要在Rust中调用它,那么main.rs
文件应该是这样的:
1 | extern crate libc; |
我们来一步一步剖析这个程序:
libc
这个库提供了许多类型定义,用以桥街Rust和C的类型。#[link(name = "extlib")]
这个Attribute
告诉编译器这个函数可以连接libextlib这个库而得到,这里的链接是动态链接。注意link
属性还有另外一种形式:#[link(name = "extlib", kind = "static")]
,kind
目前只有两个值:- static
- framework(只在OSX中)
如果不指定
kind
,那么默认为动态链接。extern
代码块声明从C语言调用的函数接口。因为编译器不知道C函数是如何实现,所以它假设内存安全问题会在你调用C函数时发生,所以我们必须用
unsafe
来包含调用C函数的语句。需要注意的是,我们需要在项目中添加一个
build.rs
文件来告诉编译如何编译C文件并生成libextlib,具体可以参考这个例子rust-to-c
安全抽象
如果我们把一个C函数绑定到Rust,不仅绑定的抽象开销为零,我们也可以让C函数变得更加安全。我们拿下面的例子来说明:
1 | // Gets the data for a file in the tarball at the given index, returning NULL if |
假设我们调用这个函数,那么函数返回一个指针,而在之后某一个时刻,我们销毁了tarball
所在内存,那么这时候这个指针就变成了野指针(dangling pointer),后续如果使用了这个指针,很可能程序就会崩溃。
如果我们把这个函数绑定到Rust:
1 | pub struct Tarball { raw: *mut tarball_t } |
data
的声明周期默认和一个Tarball
对象绑定在一起,如果Tarball
已经被销毁,那么data
由于Rust的限制也不能被使用,程序编译都不会通过,从而保证了安全。
FFI和panics
需要注意的一点是,如果其他语言函数和panic!
在同一个线程中被调用,那么结果是未定义的。如果编写的程序可能panic
,我们就得让panic
在另一个线程中被调用:
1 | use std::thread; |